/*
 * \brief  Virtual PCI bus for DDE kit
 * \author Christian Helmuth
 * \date   2008-10-22
 */

/*
 * Copyright (C) 2008-2009 Christian Helmuth
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _PCI_BUS_H_
#define _PCI_BUS_H_

#include <base/stdint.h>
#include <base/printf.h>
#include <base/lock.h>

#include <util/avl_tree.h>

#include <pci_session/connection.h>
#include <pci_device/client.h>

namespace Dde_kit {

	using namespace Genode;

	class Pci_device : public Avl_node<Pci_device>
	{
		public:

			class Not_found : public Exception { };

		private:

			unsigned char      _devfn;
			Pci::Device_client _device;

		public:

			Pci_device(unsigned char devfn, Pci::Device_capability device_cap)
			: _devfn(devfn), _device(device_cap) { }


			/***************
			 ** Accessors **
			 ***************/

			unsigned char devfn() const { return _devfn; }


			/********************************
			 ** Configuration space access **
			 ********************************/

			uint32_t config_read(unsigned char address, Pci::Device::Access_size size);

			void config_write(unsigned char address, uint32_t val,
			                  Pci::Device::Access_size size);

			/** AVL node comparison */
			bool higher(Pci_device *device) {
				return (_devfn < device->_devfn); }

			/** AVL node lookup */
			Pci_device *lookup(unsigned char devfn)
			{
				if (devfn == _devfn) return this;

				Pci_device *d = child(_devfn < devfn);

				if (!d) throw Not_found();

				return d->lookup(devfn);
			}

			/* XXX DEBUG */
			void show()
			{
				if (child(LEFT)) child(LEFT)->show();

				unsigned char ht = config_read(0x0e, Pci::Device::ACCESS_8BIT) & 0xff;

				PWRN("00:%02x.%x %04x:%04x (%x) ht=%02x",
				     _devfn >> 3, _devfn & 0x07,
				     _device.vendor_id(), _device.device_id(), _device.base_class(), ht);

				if (child(RIGHT)) child(RIGHT)->show();
			}
	};

	class Pci_bus
	{
		private:

			int                  _current_dev_num;
			Pci::Connection      _pci_drv;
			Avl_tree<Pci_device> _devices;

			Lock                 _lock;

			Pci_device *_lookup(unsigned char devfn)
			{
				if (!_devices.first()) throw Pci_device::Not_found();

				return _devices.first()->lookup(devfn);
			}

		public:

			Pci_bus();

			uint32_t config_read(unsigned char devfn, unsigned char address,
			                     Pci::Device::Access_size size)
			{
				try {
					return _lookup(devfn)->config_read(address, size);
				} catch (Pci_device::Not_found) {
					PERR("PCI device 00:%02x.%x not found", devfn >> 3, devfn & 0x07);
					return ~0;
				}
			}

			void config_write(unsigned char devfn, unsigned char address, uint32_t val,
			                  Pci::Device::Access_size size)
			{
				try {
					_lookup(devfn)->config_write(address, val, size);
				} catch (Pci_device::Not_found) {
					PERR("PCI device 00:%02x.%x not found", devfn >> 3, devfn & 0x07);
				}
			}

			/**
			 * Lookup device by device-function ID
			 *
			 * \param  devfn  device function ID
			 *
			 * \return PCI device reference
			 */
			Pci_device *lookup(unsigned char devfn)
			{
				Lock::Guard lock_guard(_lock);

				return _lookup(devfn);
			}

			/**
			 * Lookup next device
			 *
			 * \param  start  predecessor
			 *
			 * \return PCI device reference
			 */
			Pci_device *next_device(Pci_device *start)
			{
				/* return next device on bus, which is dev++ */
				unsigned char devfn = start ? start->devfn() + (1 << 3) : 0;

				return _lookup(devfn);
			}

			/* XXX DEBUG */
			void show_all() {
				if (_devices.first()) _devices.first()->show(); }


			/****************************
			 ** Class helper functions **
			 ****************************/

			static inline unsigned char devfn(unsigned char slot, unsigned char func) {
				return ((((slot) & 0x1f) << 3) | ((func) & 0x07)); }
	};

	Pci_bus* pci_bus();
}

#endif /* _PCI_BUS_H_ */
